﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

using SharpDX;
using SharpDX.Direct3D10;
using SharpDX.DXGI;
using D2D = SharpDX.Direct2D1;
using Write = SharpDX.DirectWrite;

using Device = SharpDX.Direct3D10.Device;
using Resource = SharpDX.Direct3D10.Resource;

namespace SFSpacefight
{
	public class Game : Form
	{
		// parameter constants
		public const double		GAMEUPDATEMILLI	= 40;
		public const int		width			= 1024;
		public const int		height			= 768;

		// D2D objects
		public D2D.RenderTarget target;
		public D2D.Factory		factory;
		public D2D.Brush		fpsbrush;

		// D3D objects
		public Device			device = null;
		public RenderTargetView rendertargetview;
		public SwapChain		swapchain;

		// 2D variables for rendering
		public Write.Factory			writefactory;
		public Color4					clearcolor;
		public Write.TextFormat			fpsformat;
		public RectangleF				fpslayout;

		//3D variables for rendering
		public Matrix	projectionmatrix;

		// game & state variables
		public Screen					currentscreen	{ get; private set; }
		public GameState				state			{ get; set; }
		public System.Drawing.Point		mousepos;
		public bool						clicked;
		public int						fps;

		public Game()
		{
			writefactory = new Write.Factory();
			InitializeD3D();

			// D2D drawing variables
			clearcolor = new Color4(0, 0, 0, 1f);
			fpsbrush = new D2D.SolidColorBrush(target, Color.Red);
			fpsformat = new Write.TextFormat(writefactory, "Tahoma", 12);
			fpslayout = new RectangleF(1, 1, 32, 20);

			// game & state variables
			Methods.load(target);
			currentscreen = new MenuMain(target);
			state = GameState.None;

			// prepare window
			SetClientSizeCore(width, height);
			Visible = true;
			BringToFront();
			Loop();	
		}
		public void InitializeD3D()
		{
			// DXGI D2D factories
			Factory factoryDXGI = new Factory();
			factory = new D2D.Factory();

			// D3D device
			SharpDX.Direct3D10.FeatureLevel[] levels = {
				SharpDX.Direct3D10.FeatureLevel.Level_10_1,
				SharpDX.Direct3D10.FeatureLevel.Level_10_0,
				SharpDX.Direct3D10.FeatureLevel.Level_9_3,
				SharpDX.Direct3D10.FeatureLevel.Level_9_2,
				SharpDX.Direct3D10.FeatureLevel.Level_9_1
			};
			foreach (var level in levels)
			{
				try
				{
					device = new SharpDX.Direct3D10.Device1(factoryDXGI.GetAdapter(0), DeviceCreationFlags.BgraSupport, level);
					break;
				}
				catch (Exception) { continue; }
			}

			// D3D swapchain
			SwapChainDescription swapdesc;
			swapdesc = new SwapChainDescription()
			{
				BufferCount = 1,
				IsWindowed = true,
				ModeDescription = new ModeDescription(width, height, new Rational(60, 1), Format.B8G8R8A8_UNorm),
				OutputHandle = Handle,
				Usage = SharpDX.DXGI.Usage.RenderTargetOutput,
				SampleDescription = new SampleDescription(1, 0),
				SwapEffect = SwapEffect.Discard,
				Flags = SwapChainFlags.None
			};
			swapchain = new SwapChain(factoryDXGI, device, swapdesc);
	
			// D3D rasterizer state
			RasterizerStateDescription rastDesc = new RasterizerStateDescription()
			{
				CullMode = CullMode.Back,
				FillMode = FillMode.Wireframe,
				IsMultisampleEnabled = false,
				IsAntialiasedLineEnabled = false,
				DepthBias = 0,
				DepthBiasClamp = 0,
				IsDepthClipEnabled = true,
				IsFrontCounterClockwise = false,
				IsScissorEnabled = false,
				SlopeScaledDepthBias = 0
			};
			device.Rasterizer.State = new RasterizerState(device, rastDesc);

			// D3D rasterizer viewport
			Viewport viewport;
			viewport = new Viewport(0, 0, width, height);
			viewport.MinDepth = 0;
			viewport.MaxDepth = 0;
			device.Rasterizer.SetViewports(viewport);

			// D3D backbuffer surface
			Surface backbuffer;
			backbuffer = swapchain.GetBackBuffer<Surface>(0);

			projectionmatrix = Matrix.PerspectiveFovLH((float)(Math.PI * 0.24f), width / height, 0.1f, 100.0f);

			// D3D render target view
			rendertargetview = new RenderTargetView(device, Resource.FromSwapChain<Texture2D>(swapchain, 0));
			device.OutputMerger.SetTargets(rendertargetview);

			// D2D rendertarget
			D2D.RenderTargetProperties properties = new D2D.RenderTargetProperties()
			{
				DpiX = 96,
				DpiY = 96,
				MinLevel = D2D.FeatureLevel.Level_DEFAULT,
				PixelFormat = new D2D.PixelFormat(Format.Unknown, SharpDX.Direct2D1.AlphaMode.Premultiplied),
				Usage = D2D.RenderTargetUsage.None,
			};
			target = new D2D.RenderTarget(factory, backbuffer, properties);
			target.TextRenderingParams = new Write.RenderingParams(writefactory);
		}

		public void Loop()
		{
			double tickthreshold = Environment.TickCount + GAMEUPDATEMILLI;
			double currenttick = Environment.TickCount;
			double inversegameupdate = 1F / GAMEUPDATEMILLI;

			// FPS calculations
			int frames = 0;
			double fpsthreshold = (long)tickthreshold;

			while (state != GameState.End)
			{
				do // draw loop
				{
					Draw(1.0d - inversegameupdate * (tickthreshold - currenttick));
					frames++;

					currenttick = Environment.TickCount;

					if (currenttick > fpsthreshold)
					{
						fpsthreshold += 1000;
						fps = frames;
						frames = 0;
					}
				} while (tickthreshold > currenttick);

				Message msg;

				while (PeekMessage(out msg, IntPtr.Zero, 0, 0, 1))
				{
					TranslateMessage(out msg);
					DispatchMessage(out msg);
				}

				if (msg.msg == 0x12 || this.IsDisposed) // WM_QUIT message constant
				{
					state = GameState.End;
					break;
				}

				// input handling
				tickthreshold += GAMEUPDATEMILLI;
				mousepos = PointToClient(MousePosition);

				// window message handling
				// game step
				state = currentscreen.Loop(mousepos.X, mousepos.Y, clicked);
				clicked = false; 

				if (state == GameState.NextScreen)
					currentscreen = currentscreen.NextScreen();
			}

			target.Dispose();
			factory.Dispose();
			rendertargetview.Dispose();
			swapchain.Dispose();
			device.Dispose();
			Application.Exit();
		}
		public void Draw(double percent) // percent counts UP from 0.0 to 1.0
		{
			float sin = 0.5f + 0.5f * (float)Math.Sin(Environment.TickCount * 0.006);
			float sin2 = 0.5f + 0.5f * (float)Math.Sin(Environment.TickCount * 0.005 + Math.PI * 2.0 / 3.0);
			float sin3 = 0.5f + 0.5f * (float)Math.Sin(Environment.TickCount * 0.004 + Math.PI * 4.0 / 3.0);
			Color clearbrightcolor = new Color(sin / 5, sin2 / 5, sin3 / 5, 1);
			//device.ClearRenderTargetView(rendertargetview, new Color(sin / 3, sin2 / 3, sin3 / 3, 1));

			target.BeginDraw();

			D2D.RectangleGeometry screen = new D2D.RectangleGeometry(factory, new RectangleF(0, 0, width, height));
			D2D.PathGeometry pathgeo = new D2D.PathGeometry(factory);
			D2D.GeometrySink sink = pathgeo.Open();

			screen.Combine(new D2D.RectangleGeometry(factory, new RectangleF(Width, 0, width, height)), D2D.CombineMode.Xor, sink);
			sink.Close();

			target.FillGeometry(pathgeo, new D2D.SolidColorBrush(target, clearbrightcolor));
			//target.Clear(clearcolor);

			//target.DrawText(percent.ToString(), fpsformat, fpslayout, fpsbrush);
			currentscreen.Draw(target, percent);

			target.EndDraw();

			swapchain.Present(0, PresentFlags.None);
		}

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);
		}
		protected override void OnMouseClick(MouseEventArgs e)
		{
			base.OnMouseClick(e);
			clicked = true;
		}
		protected override void OnKeyDown(KeyEventArgs e)
		{
			base.OnKeyDown(e);
		}
		protected override void OnKeyUp(KeyEventArgs e)
		{
			base.OnKeyUp(e);
		}
		protected override void OnFormClosing(FormClosingEventArgs e)
		{
			state = GameState.End;
			base.OnFormClosing(e);
		}

						   /////////////////////////
		// // // // // // /// message loop defs /// // // // // // // // //
						 /////////////////////////

		[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
		[DllImport("User32.dll", CharSet = CharSet.Auto, ExactSpelling = false)]
		public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
		[DllImport("User32.dll", CharSet = CharSet.Auto, ExactSpelling = false)]
		public static extern bool TranslateMessage(out Message msg);
		[DllImport("User32.dll", CharSet = CharSet.Auto, ExactSpelling = false)]
		public static extern bool DispatchMessage(out Message msg);

		[StructLayout(LayoutKind.Sequential)]
		public struct Message
		{
			public IntPtr hWnd;
			public Int32 msg;
			public IntPtr wParam;
			public IntPtr lParam;
			public uint time;
			public System.Drawing.Point p;
		}
	}
}
